//
// Parametric.as - Base class for single-segment parametric curves.
//
// copyright (c) 2006-2007, Jim Armstrong.  All Rights Reserved.
//
// This software program is supplied 'as is' without any warranty, express, implied, 
// or otherwise, including without limitation all warranties of merchantability or fitness
// for a particular purpose.  Jim Armstrong shall not be liable for any special incidental, or 
// consequential damages, including, without limitation, lost revenues, lost profits, or 
// loss of prospective economic advantage, resulting from the use or misuse of this software 
// program.
//
// Programmed by:  Jim Armstrong, Singularity (www.algorithmist.net)

package singularity.geom;

  import singularity.events.SingularityEvent;
  import singularity.numeric.Consts;
  import singularity.numeric.Gauss;
  
  import flash.display.Shape;
  import flash.events.EventDispatcher;
  import flash.geom.Point;
  
  typedef Pos = {
	  var x:Float;
	  var y:Float;
  }
  
  class Parametric extends EventDispatcher, implements IParametric {
    // core
    
    public var color(default, setColor) : Int;
    public var container(default, setContainer) : Shape;
    public var degree(getDegree, null) : Int ;
    public var parameterize(null, setParameterize) : String;
    public var thickness(null, setThickness) : Int;
    // core
    var __count:Int;                  // count Float of points added
    var __invalidate:Bool;          // true if current coefficients are invalid
    var __coef:IPoly;                  // polynomial curve that implements IPoly interface
    var __error:SingularityEvent;      // reference to standard Error event
    var __container:Shape;             // reference to Shape in which curve is drawn

    // Arc-length computation and parameterization
    var __arcLength:Float;            // estimate of arc length;
    //var __integrand:Dynamic;          // function to integrate to compute arc length
    var __integral:Gauss;              // Gauss-Legendre integration class
    var __param:String;                // parameterization method
    var __spline:FastCubicSpline;      // interpolate arc-length vs. t
    
    var __t:Float;                    // current t-value
    var __s:Float;                    // current arc-length

    // drawing
    var __color:Int;                // arc color
    var __thickness:Float;            // arc thickness

/**
* @description 	Method: Parameteric() - Construct a new base parametric curve
*
* @return Nothing
*
* @since 1.0
*
*/
    public function new()
    {
		super();
      __color      = 0x0000ff;
      __thickness  = 1;
      __count      = 0;
      __invalidate = true;
      __container  = null;

      __error           = new SingularityEvent(SingularityEvent.ERROR);
      __error.classname = "Parametric";

      __integral = new Gauss();
      __integral.addEventListener( SingularityEvent.ERROR, __onError );
      
      __spline = new FastCubicSpline();
      
      __param     = Consts.UNIFORM;
      __arcLength = -1;
      __t         = 0;
      __s         = 0;
    }
	
	function __integrand(_t:Float):Float
      {
        var x:Float = __coef.getXPrime(_t);
        var y:Float = __coef.getYPrime(_t);

        return Math.sqrt( x * x + y * y );
	  }

    function __onError(_e:SingularityEvent):Void
    {
      __error.message = _e.toString() + " during numerical integration.";
      dispatchEvent( __error );
    }
    
    public function getDegree():Int { return __coef.degree; }
    
    public function setParameterize(_s:String):String{
      if( _s == Consts.ARC_LENGTH || _s == Consts.UNIFORM )
      {
        __param      = _s;
        __invalidate = true;
      }
    	return _s;
     }
    
    public function setColor(_c:Int):Int{
      __color = _c;
    	return _c;
     }

    public function setThickness(_t:Int):Int{
      __thickness = Math.round(_t);
    	return _t;
     }
    
    public function setContainer(_s:Shape):Shape{
      __container = _s;
    	return _s;
     }
    
/**
* @description 	Method: arcLength() - Return arc-length of the *entire* curve by numerical integration
*
* @return Float: Estimate of total arc length of the curve
*
* @since 1.0
*
*/
    public function arcLength():Float
    {
     if ( __arcLength != -1 )
       return __arcLength;
        
      if( __invalidate )
        __computeCoef();

      var len:Float = __integral.eval( __integrand, 0, 1, 5 );
      __arcLength    = len;
      
      return len;
    }
    
/**
* @description 	Method: arcLengthAt(_t:Float) - Return arc-length of curve segment on [0,_t].
*
* @param _t:Float - parameter value to describe partial curve whose arc-length is desired
*
* @return Float: Estimate of arc length of curve segment from t=0 to t=_t.
*
* @since 1.0
*
*/
    public function arcLengthAt(_t:Float):Float
    { 
      if( __invalidate )
        __computeCoef();

      var t:Float = (_t<0) ? 0 : _t;
      t            = (t>1) ? 1 : t;
     
      return __integral.eval( __integrand, 0, t, 8 );
    }
 
/**
* @description 	Method: getCoef(_i:Int) - Return the i-th coefficient of the parameteric curve (coefficients used to evaluate the curve in nested form)
*
* @return Object - no range-checking on the argument; you break it ... you buy it!
*
* @since 1.1
*
*/
    public function getCoef(_i:Int):Dynamic
    {
      return __coef.getCoef(_i);
    }
    
    // parameterize curve
    function __parameterize():Void
    {
      if( __param == Consts.ARC_LENGTH )
      {
        if( __spline.knotCount > 0 )
          __spline.deleteAllKnots();
          
        var arcLen:Array<Dynamic> = new Array();
        var len:Float   = __integral.eval( __integrand, 0, 0.1, 8 );
        arcLen[0]        = len;
        
        len      += __integral.eval( __integrand, 0.1, 0.2, 8 );
        arcLen[1] = len;
        
        len      += __integral.eval( __integrand, 0.2, 0.3, 8 );
        arcLen[2] = len;
        
        len      += __integral.eval( __integrand, 0.3, 0.4, 8 );
        arcLen[3] = len;
        
        len      += __integral.eval( __integrand, 0.4, 0.5, 8 );
        arcLen[4] = len;
        
        len      += __integral.eval( __integrand, 0.5, 0.6, 8 );
        arcLen[5] = len;
        
        len      += __integral.eval( __integrand, 0.6, 0.7, 8 );
        arcLen[6] = len;
        
        len      += __integral.eval( __integrand, 0.7, 0.8, 8 );
        arcLen[7] = len;
        
        len      += __integral.eval( __integrand, 0.8, 0.9, 8 );
        arcLen[8] = len;
        
        len      += __integral.eval( __integrand, 0.9, 1.0, 8 );
        arcLen[9] = len;
        
        var normalize:Float = 1.0/len;

        // x-coordinate of spline knot is normalized arc-length, y-coordinate is t-value for uniform parameterization
        __spline.addControlPoint(0.0, 0.0);
        
        var l:Float = arcLen[0]*normalize;
        __spline.addControlPoint(l,0.1);
        
        l = arcLen[1]*normalize;
        __spline.addControlPoint(l,0.2);
        
        l = arcLen[2]*normalize;
        __spline.addControlPoint(l,0.3);
        
        l = arcLen[3]*normalize;
        __spline.addControlPoint(l,0.4);
        
        l = arcLen[4]*normalize;
        __spline.addControlPoint(l,0.5);
        
        l = arcLen[5]*normalize;
        __spline.addControlPoint(l,0.6);
        
        l = arcLen[6]*normalize;
        __spline.addControlPoint(l,0.7);
        
        l = arcLen[7]*normalize;
        __spline.addControlPoint(l,0.8);
        
         l = arcLen[8]*normalize;
        __spline.addControlPoint(l,0.9);

        // last control point, t=1, normalized arc-length = 1
        __spline.addControlPoint(1.0, 1.0);
      }
    }
    
    // assign the t-parameter for this evaluation
    function __setParam(_t:Float):Void
    {
      var t:Float = (_t<0) ? 0 : _t;
      t            = (t>1)  ? 1 : t;

      // if arc-length parameterization, approximate L^-1(s)
      if( __param == Consts.ARC_LENGTH )
      {
        if( t != __s )
        {
          __t = __spline.eval(t);
          __s = t;
        }
      }
      else
      {
        if( t != __t )
          __t = t;
      }
    }

    public override function toString():String
    {
      return Type.getClassName(Type.getClass(this));
    }
    
    // Pseudo-abstract methods should be implemented in subclass
    
    public function getParam(_seg:Int):Float { throw("Parametric::getParam() must be overriden"); return 0; }
    
  	public function addControlPoint( _xCoord:Float, _yCoord:Float ):Void {throw("Parametric::addControlPoint() must be overriden");}
  	
  	public function moveControlPoint(_indx:Int, _newX:Float, _newY:Float):Void {throw("Parametric::moveControlPoint() must be overriden");}
  	
  	public function getControlPoint(_indx:Int):Point { throw("Parametric::getControlPoint() must be overriden"); return null; }
  	
  	public function draw(_t:Float):Void {throw("Parametric::draw() must be overriden");}
  	
  	public function reColor(_c:Int):Void {throw("Parametric::reColor() must be overriden");}
  	
  	public function reDraw():Void {throw("Parametric::reDraw() must be overriden");}
  	
  	public function reset():Void { throw("Parametric::reset() must be overriden"); }
	public function getPos(_t:Float):Pos {throw("Parametric::getPos() must be overriden");return null;}
  	
    public function getX(_t:Float):Float {throw("Parametric::getX() must be overriden");return 0;}

    public function getY(_t:Float):Float {throw("Parametric::getY() must be overriden");return 0;}
    
    public function getXPrime(_t:Float):Float {throw("Parametric::getXPrime() must be overriden");return 0;}

    public function getYPrime(_t:Float):Float {throw("Parametric::getYPrime() must be overriden");return 0;}
    
    public function interpolate(_points:Array<Dynamic>):Void {throw("Parametric::interpolate() must be overriden");}
    
    public function __computeCoef():Void { throw("Parametric::__computeCoef() must be overriden"); }
    
    public function tAtMinX():Float { throw("Parametric::tAtMinX() not implemented in this Class");return 0; }
    
    public function tAtMinY():Float { throw("Parametric::tAtMinY() not implemented in this Class"); return 0;}
    
    public function tAtMaxX():Float { throw("Parametric::tAtMaxX() not implemented in this Class");return 0; }
    
    public function tAtMaxY():Float { throw("Parametric::tAtMaxY() not implemented in this Class"); return 0;}
  }
